﻿//2016-5-26 郑捷 创建
#include "ModbusSlaveRTU.h"
/*------------------------------------------------------------------------------------------------
MODBUSRTU 从站协议
@版本:V1.0 
@最后更新时间:2013/05/21 17:00 
@作者:Frame.Kuang
@版权申明:免费,开源
说明:
本程序处于测试阶段,支持modbus功能码03,06,16

以上为引用源程序开发者信息，感谢原作者。
2016-5-27 by 郑捷
1、修改为库形式
2、改按GB/T19582.2-2008图16推荐的RTU传输模式状态图编写
2017-12-9 by 郑捷
1、将数据库改为外部，begin函数引入数据库指针
---------------------------------------------------------------------------------------------------*/

#define microsLast(t) ((micros()+0xFFFFFFFF-t)%0xFFFFFFFF) //us计时器
#define millisLast(t) ((millis()+0xFFFFFFFF-t)%0xFFFFFFFF) //ms计时器

void ModbusSlaveRTU::begin(HardwareSerial *SerialPort,unsigned long baud,uint8_t config,
                            unsigned int *Data,unsigned int Count){
  pModbusPort=SerialPort;
  pDataTable=Data;
  DataTableCount=Count;
  (*pModbusPort).begin(baud,config);
  lastreadtime=micros();
  if (baud > 19200){  //波特率大于19200时进入条件
    characterTime = 50; //波特率大于19200时，0.1字符时间恒定为50us
  }else{
    characterTime = 1000000/baud;  //0.1字符时间
  }
}

void ModbusSlaveRTU::task(void){
  //Serial.print("modbusDataSize=");Serial.println(modbusDataSize,DEC);
  //State状态变量说明：0=初始状态，1=空闲状态，2=接收状态，3=发送状态，4=控制状态，5=控制后等待状态
  if(state==0){ //初始状态
    if(microsLast(lastreadtime)>(characterTime*35)){    //t3.5定时时间到
      (*pModbusPort).flush(); //清串口缓存
      state=1; //状态变为空闲
    }else{
      while((*pModbusPort).available()>0){ //如果串口收到数据
        (*pModbusPort).read(); //空读没用的数据
        lastreadtime=micros();  //接收到字符重启t3.5
      }
    }
  }
  if(state==1){ //空闲状态 
    if(needSendBytes>0){ //要求发送
      state=3; //状态变为发送
    }
    if((*pModbusPort).available()>0){ //串口接收到字符
      address=0; //开始接收第1个字符
      state=2; //状态变为接收
      lastreadtime=micros(); //重启t1.5和t3.5
    }
  }
  if(state==2){ //接收状态
    while((*pModbusPort).available()>0){ //收到串口数据
      if(address<bufferSize){ //接收的数据量应小于一帧数据的最大字节数量
        frame[address]=(*pModbusPort).read();
        address++;
      }else{  //条件不满足时直接清空缓冲区
        (*pModbusPort).read();
      }
      lastreadtime=micros();  //重启t1.5和t3.5
    }
    if(microsLast(lastreadtime)>(characterTime*15)){    //t1.5定时时间到
      state=4; //状态变为控制处理
    }
  }
  if(state==3){ //发送状态
    if(needSendBytes==0){
      if(microsLast(lastreadtime)>(characterTime*35)){      //t3.5定时时间到
        state=1; //状态变为空闲
      }
    }else{
      (*pModbusPort).write(&frame[0],needSendBytes);  //发送功能码16的应答数据
      needSendBytes=0; //发送完毕清发送字节数
      lastreadtime=micros(); //重启t3.5
    }
  }
  if(state==4){ //控制和处理
    dataHandle();
    state=5; //状态变为等待状态
  }
  if(state==5){ //控制和处理后等待状态
    if(microsLast(lastreadtime)>(characterTime*35)){    //t3.5定时时间到
      state=1; //状态变为空闲
    }
  }
}

void ModbusSlaveRTU::dataHandle(void){
  unsigned int crc16;  //校验位
  unsigned char function=frame[1];  //读取功能码      
  if(frame[0]==SlaveID || frame[0]==0){  //站号匹配或者消息为广播形式,进入条件
    crc16 = ((frame[address - 2] << 8) | frame[address - 1]);
    if(calculateCRC(&frame[0],address - 2)==crc16){  //数据校验通过,进入条件
      if (frame[0]!=0 && (function == 3)){  //功能码03不支持广播消息
        unsigned int startData=((frame[2] << 8) | frame[3]);  //读取modbus数据库起始地址           
        unsigned int dataSize=((frame[4] << 8) | frame[5]);  //需要读取的modbus数据库数据长度
        unsigned int endData=startData+dataSize;    //需要读取的modbus数据库数据的结束地址
        unsigned char responseSize=5+dataSize*2;  //计算应答的数据长度
        unsigned int temp;
        if(endData>DataTableCount || responseSize>bufferSize){  
        //读取数据的结束地址超过了modbus数据库的范围或单次读取的数据数量大于缓冲
          responseError(0x02);  //返回错误消息：数据超过范围
        }else{
          frame[0]=SlaveID;  //设定站号
          frame[1]=function;  //设定功能码
          frame[2]=dataSize*2;  //设定数据长度
          unsigned int index=3;
          for(unsigned int i=startData;i<endData;i++){
            temp=*(pDataTable+i);  //取出modbus数据库中的数据
            frame[index]=temp>>8;
            index++;
            frame[index]=temp & 0xFF;
            index++;
          }
          crc16 = calculateCRC(&frame[0],responseSize-2);
          frame[responseSize-2] = crc16>>8;  //填写校验位
          frame[responseSize-1] = crc16 & 0xFF;
          needSendBytes=responseSize;  //返回功能码03的消息
        }
      }else if(function == 6){  //功能码为06时进入条件
        unsigned int startData=((frame[2] << 8) | frame[3]);  //写入modbus数据库的地址           
        unsigned int setData=((frame[4] << 8) | frame[5]);  //写入modbus数据库的数值
        if(startData>=DataTableCount){
           responseError(0x02);  //返回错误消息：数据超过范围
        }else{
          *(pDataTable+startData)=setData;  //写入数据到modbus数据库
          if(frame[0]!=0){   //广播方式下不用返回消息
            //功能6的返回消息与指令一致              
            needSendBytes=8;  //返回功能码06的消息              
          }
        }
      }else if(function == 16){  //功能码为16时进入条件
        if(frame[6]!=address-9){  //校验数据长度
          responseError(0x03);  //返回错误消息：数据长度不符
        }else{  //校验数据长度正确
          unsigned int startData=((frame[2] << 8) | frame[3]);  //写入modbus数据库起始地址           
          unsigned int dataSize=((frame[4] << 8) | frame[5]);  //需要写入的modbus数据库数据长度
          unsigned int endData=startData+dataSize;    //需要写入的modbus数据库数据的结束地址
          if(endData>DataTableCount){  //读取数据的结束地址超过了modbus数据库的范围
            responseError(0x02);  //返回错误消息：数据超过范围
          }else{              
            unsigned int index;
            unsigned int setData;
            index = 7;  //从数据贞的第8个数据开始读取            
            for(unsigned int i=startData;i<endData;i++){
              setData=(frame[index]<<8|frame[index+1]);  //将数据写入modbus数据库中
              *(pDataTable+i)=setData;
              index+=2;
            }
            if(frame[0]!=0){   //广播方式下不用返回消息
              frame[0]=SlaveID;  //填写站号
              //frame[1]到frame[5]不变
              crc16 = calculateCRC(&frame[0],6);  //计算CRC校验
              frame[6] = crc16>>8;  //填写校验位
              frame[7] = crc16 & 0xFF;
              needSendBytes=8;  //发送功能码16的应答数据 
            }
          }
        }
      }else{  //其他功能码
        responseError(0x01);  //返回错误消息：不支持收到的功能码
      }      
    }else{ //数据校验错误
      responseError(0x03);  //返回错误消息
    }
  }
}

//错误信息返回函数
void ModbusSlaveRTU::responseError(unsigned char wrongNumber){
  unsigned int crc16;  //校验位  
  if(frame[0] == SlaveID){  //非广播帧
    frame[1] = frame[1]+0x80;
    frame[2] = wrongNumber;  //填写错误代码
    crc16 = calculateCRC(&frame[0],3);  //计算校验值
    frame[3] = crc16 >> 8;  //填写校验位
    frame[4] = crc16 & 0xFF;
    needSendBytes=5;  //返回错误代码
  }else{
    needSendBytes=0;
  }
}

//CRC校验函数
//参数1:待校验数组的起始地址
//参数2:待校验数组的长度
//返回值CRC校验结果,16位,低字节在前
unsigned int ModbusSlaveRTU::calculateCRC1(unsigned char* regs,unsigned char arraySize){
  unsigned int temp, temp2, flag;
  temp = 0xFFFF;
  for (unsigned char i = 0; i < arraySize; i++){
    temp = temp ^ *(regs+i);
    for (unsigned char j = 1; j <= 8; j++){
      flag = temp & 0x0001;
      temp >>= 1;
      if (flag)
        temp ^= 0xA001;
    }
  }
  temp2 = temp >> 8;
  temp = (temp << 8) | temp2;
  temp &= 0xFFFF; 
  return temp; 
}

//CRC校验函数（查表法）
/* CRC 高位字节值表 */    
const unsigned char c_aucCRCHi[] = {    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,    
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,    
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,    
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,    
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,    
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,    
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,    
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,    
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,    
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,    
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,    
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,    
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,    
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,    
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40    
} ;    
   
/* CRC低位字节值表*/    
const unsigned char c_aucCRCLo[] = {    
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,    
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,    
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,    
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,    
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,    
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,    
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,    
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,    
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,    
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,    
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,    
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,    
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,    
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,    
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,    
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,    
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,    
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,    
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,    
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,    
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,    
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,    
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,    
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,    
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,    
0x43, 0x83, 0x41, 0x81, 0x80, 0x40    
} ;   

unsigned int ModbusSlaveRTU::calculateCRC(unsigned char* regs,unsigned char arraySize){    
  unsigned int uiCRCHi = 0xFF ;              /* 高CRC字节初始化  */    
  unsigned int uiCRCLo = 0xFF ;              /* 低CRC 字节初始化 */    
  unsigned char uIndex ;                      /* CRC循环中的索引  */    
  while (arraySize--){                  /* 传输消息缓冲区   */    
    uIndex = (char)uiCRCHi ^ *regs++ ; /* 计算CRC          */    
    uiCRCHi = uiCRCLo ^ (int)c_aucCRCHi[uIndex] ;    
    uiCRCLo = (int)c_aucCRCLo[uIndex] ;    
  }    
  return (uiCRCHi << 8 | uiCRCLo) ;    
}    
